#ifndef DATAFLOW_GRAPH_H
#define DATAFLOW_GRAPH_H

#include <assert.h>
#include <utility>
#include <algorithm>

#include "GraphNode.h"
#include "GraphNodeVector.h"
#include "GraphLink.h"
#include "GraphLinkSet.h"

namespace DataFlow
{

namespace Detail
{

class Scheduler;

}

class PIN
{
	public:

		typedef void BaseType;
		typedef PIN  ThisType;

		PIN(int node, int port) : n(node), p(port) { ; }

		int node(void) const { return this->n; }
		int port(void) const { return this->p; }

	protected:

		int n;
		int p;
};

class INPUT : public PIN
{
	public:

		typedef PIN   BaseType;
		typedef INPUT ThisType;

		INPUT(int node, int port) : BaseType(node, port) { ; }
};

class SINK : public INPUT
{
	public:

		typedef INPUT BaseType;
		typedef SINK  ThisType;

		enum { ID = -2 };

		SINK(int port) : BaseType(ThisType::ID, port) { ; }
};

class OUTPUT : public PIN
{
	public:

		typedef PIN   BaseType;
		typedef OUTPUT ThisType;

		OUTPUT(int node, int port) : BaseType(node, port) { ; }
};

class SOURCE : public OUTPUT
{
	public:

		typedef OUTPUT BaseType;
		typedef SOURCE ThisType;

		enum { ID = -1 };

		SOURCE(int port) : BaseType(ThisType::ID, port) { ; }
};

class Graph
{
	friend class Detail::Scheduler;

	public:

		typedef void  BaseType;
		typedef Graph ThisType;

		enum
		{
			SOURCE_ID = SOURCE::ID,
			SINK_ID   = SINK::ID
		};

		Graph(int size, int sourceOutputSize, int sinkInputSize)
			: sourceOutCount(sourceOutputSize)
			, sinkInCount(sinkInputSize)
		{
			assert(size             >= 0);
			assert(sourceOutputSize >= 0);
			assert(sinkInputSize    >= 0);

			this->gnodes.resize(size_t(size));
			for (size_t i=0; i<this->gnodes.size(); ++i)
			{
				this->gnodes[i].id = int(i);
			}
		}

		int size(void) const
		{
			return int(this->gnodes.size());
		}

		void setNode(int node, int inputsCount, int outputsCount, int maxTime, Task * task)
		{
			assert((node >= 0) && (node < int(this->gnodes.size())));
			assert(inputsCount  >= 0);
			assert(outputsCount >= 0);

			if (maxTime < 0) maxTime = 0;

			/*
			// mingw bug
			LinkRefersNode pred(node);
			std::remove_if(this->glinks.begin(), this->glinks.end(), pred);
			*/

			Detail::LinkRefersNode pred(node);
			std::vector<Detail::GraphLinkSetIterator> its;
			for (Detail::GraphLinkSetIterator it=this->glinks.begin(); it!=this->glinks.end(); ++it) { if (pred(*it)) { its.push_back(it); } }
			for (size_t i=0; i<its.size(); ++i) { this->glinks.erase(its[i]); }

			Detail::GraphNode & n = this->gnodes[node];
			n.inputSize  = inputsCount;
			n.outputSize = outputsCount;
			n.maxTime    = maxTime;
			n.task       = task;
		}

		void link(const OUTPUT & outputPin, const INPUT & inputPin, int capacity = 0)
		{
			this->link(outputPin.node(), outputPin.port(), inputPin.node(), inputPin.port(), capacity);
		}

		void unlink(const OUTPUT & outputPin, const INPUT & inputPin)
		{
			this->unlink(outputPin.node(), outputPin.port(), inputPin.node(), inputPin.port());
		}

		void hasLink(const OUTPUT & outputPin, const INPUT & inputPin)
		{
			this->hasLink(outputPin.node(), outputPin.port(), inputPin.node(), inputPin.port());
		}

	protected:

		Detail::GraphNodeVector gnodes;
		Detail::GraphLinkSet    glinks;

		int sourceOutCount;
		int sinkInCount;

		const Detail::GraphNodeVector & nodes(void) const
		{
			return this->gnodes;
		}

		const Detail::GraphLinkSet & links(void) const
		{
			return this->glinks;
		}

		int sourceOutputsCount(void) const
		{
			return this->sourceOutCount;
		}

		int sinkInputsCount(void) const
		{
			return this->sinkInCount;
		}

		void link(int sourceNode, int sourcePort, int targetNode, int targetPort, int capacity)
		{
			if (capacity < 0 ) capacity = 0;

			assert
			(
				((sourceNode == ThisType::SOURCE_ID) && (sourcePort >= 0) && (sourcePort < this->sourceOutCount)) ||
				((sourceNode >= 0) && (sourceNode < int(this->gnodes.size())) && (sourcePort >= 0) && (sourcePort < this->gnodes[sourceNode].outputSize))
			);

			assert
			(
				((targetNode == ThisType::SINK_ID) && (targetPort >= 0) && (targetPort < this->sinkInCount)) ||
				((targetNode >= 0) && (targetNode < int(this->gnodes.size())) && (targetPort >= 0) && (targetPort < this->gnodes[targetNode].inputSize))
			);

			Detail::LinkRefersNodeInput pred(targetNode, targetPort);
			std::vector<Detail::GraphLinkSetIterator> its;
			for (Detail::GraphLinkSetIterator it=this->glinks.begin(); it!=this->glinks.end(); ++it) { if (pred(*it)) { its.push_back(it); } }
			for (size_t i=0; i<its.size(); ++i) { this->glinks.erase(its[i]); }

			Detail::GraphLink gl;
			gl.sourceNode  = sourceNode;
			gl.sourcePort  = sourcePort;
			gl.targetNode  = targetNode;
			gl.targetPort  = targetPort;
			gl.capacity    = capacity;
			this->glinks.insert(gl);
		}

		void unlink(int sourceNode, int sourcePort, int targetNode, int targetPort)
		{
			assert
			(
				((sourceNode == ThisType::SOURCE_ID) && (sourcePort >= 0) && (sourcePort < this->sourceOutCount)) ||
				((sourceNode >= 0) && (sourceNode < int(this->gnodes.size())) && (sourcePort >= 0) && (sourcePort < this->gnodes[sourceNode].outputSize))
			);

			assert
			(
				((targetNode == ThisType::SINK_ID) && (targetPort >= 0) && (targetPort < this->sinkInCount)) ||
				((targetNode >= 0) && (targetNode < int(this->gnodes.size())) && (targetPort >= 0) && (targetPort < this->gnodes[targetNode].inputSize))
			);

			Detail::GraphLink gl;
			gl.sourceNode = sourceNode;
			gl.sourcePort = sourcePort;
			gl.targetNode = targetNode;
			gl.targetPort = targetPort;
			this->glinks.erase(gl);
		}

		bool hasLink(int sourceNode, int sourcePort, int targetNode, int targetPort)
		{
			assert
			(
				((sourceNode == ThisType::SOURCE_ID) && (sourcePort >= 0) && (sourcePort < this->sourceOutCount)) ||
				((sourceNode >= 0) && (sourceNode < int(this->gnodes.size())) && (sourcePort >= 0) && (sourcePort < this->gnodes[sourceNode].outputSize))
			);

			assert
			(
				((targetNode == ThisType::SINK_ID) && (targetPort >= 0) && (targetPort < this->sinkInCount)) ||
				((targetNode >= 0) && (targetNode < int(this->gnodes.size())) && (targetPort >= 0) && (targetPort < this->gnodes[targetNode].inputSize))
			);

			Detail::GraphLink gl;
			gl.sourceNode = sourceNode;
			gl.sourcePort = sourcePort;
			gl.targetNode = targetNode;
			gl.targetPort = targetPort;

			return (this->glinks.count(gl) > 0);
		}
};

}

#endif // DATAFLOW_GRAPH_H
